package game;

import com.sun.j3d.utils.image.TextureLoader;
import physics.Circle;
import physics.CollisionHandler;
import physics.PhysicsObject;
import physics.Pocket;
import physics.Polygon;

import javax.media.j3d.*;
import javax.vecmath.Point2f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector2f;
import java.io.File;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Table {

    public List<Circle> balls;


    public BranchGroup BG;
    public TransformGroup TG;

    private static final float width = .676f;
    private static final float height = .338f;
    public float ballRadius = .018f * width*2;

    private final Pocket[] pockets;
    private List<Polygon> edges;

    /**
     * Constructs the view and boundaries for the table.
     */
    public Table() {
        balls = new ArrayList<Circle>();
        edges = new ArrayList<Polygon>();
        BG = new BranchGroup();
        BG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        BG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        BG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);

        TG = new TransformGroup();
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_READ);

        BG.addChild(TG);


        pockets = new Pocket[] {new Pocket(-width, height, ballRadius*1.5f), 
        		new Pocket(-width, -height, ballRadius*1.5f),
        		new Pocket(0, -height-ballRadius*1.5f, ballRadius*1.5f),
        		new Pocket(width, -height, ballRadius*1.5f),
        		new Pocket(width, height, ballRadius*1.5f),
        		new Pocket(0, height+ballRadius*1.5f, ballRadius*1.5f)};

        for (Pocket p : pockets) {
//            TG.addChild(p.BG);
        }

        createBoundaries();
        createShape();
    }

    /**
     * Gets a single boundary
     * @return a trapezoid boundary shape
     */
    public Polygon getBoundary() {
        float edgeSize = .07f;
        float bezel = .06f;
        Polygon edge = new Polygon();
        edge.vertices = new float[] {-edgeSize/2f,-height, edgeSize/2f,-height+bezel, edgeSize/2f,height-bezel, -edgeSize/2f,height };
        edge.mass = Float.POSITIVE_INFINITY;
        return edge;
    }

    /**
     * Create the boundaries for the table
     */
    public void createBoundaries() {
        Polygon edge;
        float edgeSize = .07f;

        //left
        edge = getBoundary();
        edge.position = new Vector2f(-width - edgeSize/2f, 0);
        edge.init();
        edges.add(edge);

        //right
        edge = getBoundary();
        edge.position = new Vector2f(width + edgeSize/2f, 0);
        edge.orientation = (float)Math.PI;
        edge.init();
        edges.add(edge);

        //top left
        edge = getBoundary();
        edge.position = new Vector2f(-width/2f, height + edgeSize / 2f);
        edge.orientation = (float)-Math.PI / 2f;
        edge.vertices[7] -= .03f;
        edge.vertices[5] += .02f;
        edge.init();
        edges.add(edge);

        //top right
        edge = getBoundary();
        edge.position = new Vector2f(width/2f, height + edgeSize / 2f);
        edge.orientation = (float)-Math.PI / 2f;
        edge.vertices[3] -= .02f;
        edge.vertices[1] += .03f;
        edge.init();
        edges.add(edge);

        // bottom left
        edge = getBoundary();
        edge.position = new Vector2f(-width/2f, -height - edgeSize / 2f);
        edge.orientation = (float)Math.PI / 2f;
        edge.vertices[3] -= .02f;
        edge.vertices[1] += .03f;
        edge.init();
        edges.add(edge);

        //bottom right
        edge = getBoundary();
        edge.position = new Vector2f(width/2f, -height - edgeSize / 2f);
        edge.orientation = (float)Math.PI / 2f;
        edge.vertices[7] -= .03f;
        edge.vertices[5] += .02f;
        edge.init();
        edges.add(edge);

        for (Polygon e : edges) {
//            TG.addChild(e.BG);
        }
    }

    /**
     * Restricts the cue ball position when in scratch mode
     * @param ball Cue ball
     */
    public void restrictCueBallPosition(CueBall ball) {
        ball.position.x = Math.max(-width + ball.radius, Math.min(ball.position.x, (0 - width/2f) - ball.radius));
        ball.position.y = Math.max(-height + ball.radius, Math.min(ball.position.y, height - ball.radius));
    }

    /**
     * Updates balls and performs collision detection
     * @param delta Time elapsed
     */
    public void tick(float delta) {
        for (PhysicsObject o : balls) {
            o.velocity.scale(.99f);
            o.angularVelocity *= .99f;
            o.updateState(delta);
        }
        App.cueBall.velocity.scale(.99f);
        App.cueBall.angularVelocity *= .99f;
        App.cueBall.updateState(delta);
        for (int i = 0; i < 1; i++) {
            Iterator<Circle> it = balls.iterator();
            while (it.hasNext()) {
                PhysicsObject o = it.next();

                CollisionHandler.checkAndResolveCollision(App.cueBall, o);

                for (PhysicsObject o2 : balls){
                    CollisionHandler.checkAndResolveCollision(o2, o);
                }

                for (PhysicsObject p : edges) {
                    CollisionHandler.checkAndResolveCollision(o, p);
                }

                for (Pocket p : pockets) {
                    checkPocket((Circle)o, p, it);
                }
            }
        }

        for (Pocket p : pockets) {
            checkPocket(App.cueBall, p, null);
        }

        for (PhysicsObject p : edges) {
            CollisionHandler.checkAndResolveCollision(App.cueBall, p);
        }

        for (PhysicsObject o : balls) {
            o.updateTransformGroup();
            // Clear the object's force accumulator.
            o.forceAccumulator.x = o.forceAccumulator.y = 0;
        }

        App.cueBall.updateTransformGroup();
        App.cueBall.forceAccumulator.x = App.cueBall.forceAccumulator.y = 0;

        if (balls.size() == 0) {
            App.cueBall.velocity = new Vector2f();
            rack();
            App.setState(App.State.SCRATCH);
            App.resetScore();
        }
    }

    /**
     * Checks whether a circle has made it into the pocket
     * @param c Circle
     * @param p Pocket
     * @param it Iterator for removal
     */
    public void checkPocket(Circle c, Pocket p, Iterator<Circle> it) {
        Point2f cp = new Point2f(c.position.x, c.position.y);
        Point2f pp = new Point2f(p.position.x, p.position.y);
        float distance = cp.distance(pp);
        if (distance < p.radius) {
            if (c instanceof CueBall) {
                App.isScratched = true;
                c.velocity.x = c.velocity.y = 0;
            } else {
                it.remove(); // remove from balls list
                c.BG.detach(); // remove from view
                App.score();
            }
        }
    }

    /**
     * Re-position the balls
     */
    public void rack() {

        App.cueBall.position.x = -width + width/4f;
        App.cueBall.position.y = 0;
        App.cueBall.updateTransformGroup();


        //r remove from tg
        for (Circle c : balls) {
            c.BG.detach();
        }

        // clear list
        balls.clear();

        // create new
        for (int col = 0; col < 3; col++) {

            int ballCount = col + 1;
            float x = 0.25f * width + col * ballRadius * (float)Math.sqrt(3);
            float yStart = -col * ballRadius;

            for (int row = 0; row < ballCount; row++) {
                Circle c = new Circle(1,
                        x, yStart + row * ballRadius * 2,
                        0, 0,
                        0, 0, ballRadius, null, null, balls.size() + 1);
                TG.addChild(c.BG);
                balls.add(c);
            }
        }
    }

    /**
     * Check whether all balls are stopped
     * @return True if stopped
     */
    public boolean areBallsStopped() {
        float delta = .015f;
        for (Circle c : balls) {
            if (c.velocity.length() > delta) {
                return false;
            }
            c.velocity.x = c.velocity.y = 0;
        }
        if (App.cueBall.velocity.length() > delta) {
            return false;
        }
        App.cueBall.velocity.x = App.cueBall.velocity.y = 0;
        return true;
    }

    /**
     * Creates the view for the table
     */
    public void createShape() {
        try
        {
            TextureLoader myLoader = new TextureLoader( new File("src/table.png").toURI().toURL(), null);

            ImageComponent2D myImage = myLoader.getImage( );

            Raster raster = new Raster( );
            raster.setPosition(new Point3f(-.752f, 0.4125f, -.01f));
            raster.setType(Raster.RASTER_COLOR);
            raster.setSize(600, 329);
            raster.setImage( myImage );

            Appearance app = new Appearance();
            TG.addChild(new OrientedShape3D(raster, app, OrientedShape3D.ROTATE_ABOUT_POINT, new Point3f(0.0f, 0.0f, 0.0f)));
        }
        catch (MalformedURLException e) { throw new RuntimeException(); }
    }


}
